#pragma once
#include <glm/gtc/type_ptr.hpp>
#include "./Object3D.hpp"

// Assume Object3D::m_localScale
class Camera3D : public Object3D {
public:
    enum Mode : glm::uint32_t {
        Perspective = 0,
        Orthographic = 1,
    };

    // m_needUpdateMatrix.bit[0, 1] : worldMatrix & viewMatrix, projectionMatrix
    FORCEINLINE float fov() const { return m_fov; }
    FORCEINLINE void setFov(float f) {
        m_fov = f;
        m_needUpdateMatrix = 1;
    }
    FORCEINLINE float aspectRatio() const { return m_aspectRatio; }
    FORCEINLINE void setAspectRatio(float r) {
        m_aspectRatio = r;
        m_needUpdateMatrix = 1;
    }
    FORCEINLINE float nearPlane() const { return m_nearPlane; }
    FORCEINLINE void setNearPlane(float np) {
        m_nearPlane = np;
        m_needUpdateMatrix = true;
    }
    FORCEINLINE float farPlane() const { return m_farPlane; }
    FORCEINLINE void setFarPlane(float fp) {
        m_farPlane = fp;
        m_needUpdateMatrix = true;
    }
    FORCEINLINE void updateCameraMatrix() {
        m_viewMatrix = glm::inverse(worldMatrix());
        m_projectionMatrix = glm::perspective(glm::radians(m_fov), m_aspectRatio, m_nearPlane, m_farPlane);
        m_viewProjectionMatrix = m_projectionMatrix * m_viewMatrix;
    }
    FORCEINLINE auto viewProjectionMatrix() const { return m_viewProjectionMatrix; }
    FORCEINLINE auto viewProjectionMatrixPtr() const { return glm::value_ptr(m_viewProjectionMatrix); }

protected:
    virtual void update(float ms) {
        if (m_needUpdateMatrix) {
            Object3D::updateMatrix();
            updateCameraMatrix();
            m_needUpdateMatrix = 0;
        }
        // updateChildren(ms);
    }

    float m_fov = 45.0f; // In degrees
    float m_aspectRatio = 4.0f / 3.0f;
    float m_nearPlane = 0.1f;
    float m_farPlane = 1000.0f;
    glm::vec3 m_target{1.0f, 0.0f, 0.0f};
    Mode m_mode{Perspective};

    glm::mat4 m_viewMatrix{1.0f};
    glm::mat4 m_projectionMatrix{1.0f};
    glm::mat4 m_viewProjectionMatrix{1.0f};
};